// Mechanicoder
// 2023/02/19

#include "view_window.h"
#include "View.h"
#include "ui_ctrl_widget.h"
#include "test_helix.h"

#include <STEPControl_Reader.hxx>
#include <STEPControl_Writer.hxx>
#include <Graphic3d_ArrayOfPoints.hxx>
#include <AIS_Shape.hxx>
#include <AIS_PointCloud.hxx>
#include <AIS_InteractiveObject.hxx>
#include <Prs3d_PointAspect.hxx>
#include <Prs3d_Drawer.hxx>
#include <Graphic3d_AspectFillArea3d.hxx>
#include <BRepOffsetAPI_MakeOffset.hxx>
#include <BRepBuilderAPI_MakeVertex.hxx>
#include <BRepBuilderAPI_MakeWire.hxx>
#include <BRepBuilderAPI_MakeFace.hxx>
#include <BRepBuilderAPI_Transform.hxx>
#include <BRepBuilderAPI_MakePolygon.hxx>
#include <BRepBuilderAPI_Copy.hxx>
#include <BRepLib.hxx>
#include <BRepBndLib.hxx>
#include <BRep_Tool.hxx>
#include <BRep_Builder.hxx>
#include <ShapeAnalysis.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Wire.hxx>
#include <TopoDS_Shape.hxx>
#include <TopExp.hxx>
#include <TopExp_Explorer.hxx>
#include <Standard_Version.hxx>
#include <AIS_InteractiveContext.hxx>
#include <BRepFill_Filling.hxx>
#include <ShapeFix_Face.hxx>
#include <Quantity_Color.hxx>
#include <Bnd_OBB.hxx>
#include <Geom2dAPI_Interpolate.hxx>
#include <Geom2d_BSplineCurve.hxx>
#include <Geom_BSplineSurface.hxx>

#include <QHBoxLayout>
#include <QFileDialog>
#include <QDebug>
#include <QLineEdit>
#include <QKeyEvent>
#include <QMessageBox>
#include <QTimer>

namespace
{
    void TestPeriodic()
    {
        gp_Pnt2d p1(0, 0);
        gp_Pnt2d p2(0, 10);
        gp_Pnt2d p3(5, 5);
        Handle(TColgp_HArray1OfPnt2d) pnts = new TColgp_HArray1OfPnt2d(1, 3);
        pnts->SetValue(1, p1);
        pnts->SetValue(2, p2);
        pnts->SetValue(3, p3);
        Geom2dAPI_Interpolate interp(pnts, true, Precision::Confusion());
        interp.Perform();
        if (!interp.IsDone())
        {
            qDebug() << "插值失败";
        }

        Handle(Geom2d_BSplineCurve) bc = interp.Curve();
        std::vector<double> flat_knots(bc->KnotSequence().begin(), bc->KnotSequence().end());
        std::vector<gp_Pnt2d> ctrl_pnts(bc->NbPoles());
        for (size_t i = 0; i < ctrl_pnts.size(); i++)
        {
            ctrl_pnts[i] = bc->Pole(int(i + 1));
        }
        const int degree = bc->Degree();
        qDebug() << "Degree " << degree;
        qDebug() << "Flat Knots " << flat_knots;
    }

    std::vector<std::vector<gp_Pnt>> MakeSurfGridPnts(const Handle(Geom_Surface)& surf)
    {
        if (surf)
        {
            Handle(Geom_BSplineSurface) bs = Handle(Geom_BSplineSurface)::DownCast(surf);
            if (bs)
            {
                if (bs->IsVPeriodic())
                {
                    std::vector<std::vector<gp_Pnt>> all_pnts;
                    double u1 = 0, u2 = 0, v1 = 0, v2 = 0;
                    bs->Bounds(u1, u2, v1, v2);
                    const size_t N = 20;
                    for (size_t j = 0; j < N; j++)
                    {
                        double v = v1 + (v2 - v1) * double(j) / double(N - 1);
                        std::vector<gp_Pnt> pnts;
                        for (size_t i = 0; i < N; i++)
                        {
                            double u = u1 + (u2 - u1) * double(i) / double(N - 1);
                            pnts.emplace_back(bs->Value(u, v));
                        }
                        all_pnts.emplace_back(pnts);
                    }

                    return all_pnts;
                }
            }
        }
        return {};
    }

    TopoDS_Shape Compound(const std::vector<TopoDS_Shape>& shapes)
    {
        TopoDS_Compound comp;
        BRep_Builder builder;
        builder.MakeCompound(comp);
        for (const auto& s : shapes)
        {
            if (!s.IsNull())
            {
                builder.Add(comp, s);
            }
        }
        return comp;
    }

    TopoDS_Shape MakeGridShape(const std::vector<std::vector<gp_Pnt>>& all_pnts)
    {
        std::vector<TopoDS_Shape> polys;
        for (const auto& pnts : all_pnts)
        {
            BRepBuilderAPI_MakePolygon poly;
            for (const auto& p : pnts)
            {
                poly.Add(p);
            }
            polys.emplace_back(poly.Shape());
        }
        return Compound(polys);
    }

    void TestSetOrigin(const std::vector<TopoDS_Shape>& selected, ModelList* ml)
    {
        TopoDS_Face face;
        for (const auto& f : selected)
        {
            if (!f.IsNull() && TopAbs_FACE == f.ShapeType())
            {
                face = TopoDS::Face(f);
            }
        }

        Handle(Geom_Surface) surf = BRep_Tool::Surface(face);
        if (surf)
        {
            Handle(Geom_BSplineSurface) bs = Handle(Geom_BSplineSurface)::DownCast(surf);
            if (bs->IsVPeriodic())
            {
                auto all_pnts = MakeSurfGridPnts(bs);
                if (!all_pnts.empty())
                {
                    TopoDS_Shape pnts_shape = MakeGridShape(all_pnts);
                    ml->RecordModel(pnts_shape, "Before");
                }

                double u1 = 0, u2 = 0, v1 = 0, v2 = 0;
                bs->Bounds(u1, u2, v1, v2);
                bs->SetVOrigin((bs->FirstVKnotIndex() + bs->LastVKnotIndex()) / 2);

                all_pnts = MakeSurfGridPnts(bs);
                if (!all_pnts.empty())
                {
                    TopoDS_Shape pnts_shape = MakeGridShape(all_pnts);
                    ml->RecordModel(pnts_shape, "After");
                }
            }
        }
    }

}

struct ViewWindow::Data
{
    Handle(AIS_InteractiveContext) context;

    View* view;
    Ui::CtrlWidgetUi* ctrlUi;

    std::vector<Handle(AIS_InteractiveObject)> generated;

    gp_XYZ bndCenter; // 边界中心

    bool surfDeformRunning;
    std::vector<gp_Pnt2d> surfDeformUVs;
    std::vector<double> surfDeformOffsets;
    std::vector<double> surfDeformOffsetSigns;
    double surfDeformMaxOffset;
};

ViewWindow::ViewWindow(QWidget* parent)
    : QMainWindow(parent), _d(std::make_shared<Data>())
{
    _d->context = ViewHelper::InitContext(ViewHelper::InitViewer());
    _d->view = new View({ _d->context }, this);
    _d->surfDeformRunning = false;

    _d->view->top();

    this->setCentralWidget(_d->view);

    InitCtrlWidget();

    this->resize(800, 600);

    //DrawShape(ViewHelper::MakeBottle(80, 100, 20), "Bottle");

    TestPeriodic();
}

ViewWindow::~ViewWindow()
{
}

void ViewWindow::OnImport()
{
    QString filename;
    filename = QFileDialog::getOpenFileName(this, "Select STEP file", "", "*.stp");
    QFileInfo info(filename);
    if (!info.exists())
    {
        return;
    }

    // Clear All
    _d->ctrlUi->listViewDisplay->Clear();

    STEPControl_Reader reader;
    TCollection_AsciiString  aFilePath = filename.toUtf8().data();
    IFSelect_ReturnStatus status = reader.ReadFile(aFilePath.ToCString());

    int nbr = reader.NbRootsForTransfer();
    for (Standard_Integer n = 1; n <= nbr; n++)
    {
        reader.TransferRoot(n);
    }

    // 显示模型
    int nbs = reader.NbShapes();
    for (int i = 1; i <= nbs; i++)
    {
        TopoDS_Shape shape = reader.Shape(i);
        Handle(AIS_Shape) ais = new AIS_Shape(shape);
        _d->ctrlUi->listViewDisplay->RecordModel(ais, QString("Import %1").arg(i));
    }
    _d->view->fitAll();

    _d->context->Activate(ViewHelper::SM_FACE);
    _d->context->Activate(ViewHelper::SM_EDGE);
    _d->context->SetIsoNumber(3);
    _d->view->top();
}

void ViewWindow::OnHideShape()
{
    AIS_ListOfInteractive objs;
    _d->context->ErasedObjects(objs);
    if (objs.IsEmpty())
    {
        _d->context->EraseAll(true);
    }
    else
    {
        _d->context->DisplayAll(true);
    }
}

void ViewWindow::InitCtrlWidget()
{
    QWidget* parent = this;
    QWidget* widget = new QWidget(parent);

    _d->ctrlUi = new Ui::CtrlWidgetUi;
    _d->ctrlUi->setupUi(widget);
    widget->setAutoFillBackground(true);

    _d->ctrlUi->listViewDisplay->SetupContext(_d->context);

    QHBoxLayout* hlayout = new QHBoxLayout();
    hlayout->addWidget(widget);
    hlayout->addStretch();

    QVBoxLayout* vlayout = new QVBoxLayout(widget);
    vlayout->addLayout(hlayout);
    vlayout->addStretch();

    connect(_d->ctrlUi->pushButton_import, &QPushButton::clicked, this, &ViewWindow::OnImport);

    widget->move(1, 1);
    widget->setWindowOpacity(0.6);

    widget->updateGeometry();
    widget->show();
}

void ViewWindow::DrawShape(const TopoDS_Shape& shape, const QString& name, int color/* = -1*/) const
{
    Handle(AIS_Shape) ais = new AIS_Shape(shape);
    if (_d->ctrlUi)
    {
        _d->ctrlUi->listViewDisplay->RecordModel(ais, name, color);
    }
}

bool ViewWindow::IsAllCurve3D(const TopoDS_Shape& shape) const
{
    TopTools_IndexedMapOfShape sub_shapes;
    TopExp::MapShapes(shape, TopAbs_EDGE, sub_shapes); // wire 或 面的外环
    for (auto it = sub_shapes.cbegin(); it != sub_shapes.cend(); ++it)
    {
        TopoDS_Edge edge = TopoDS::Edge(*it);
        double cf = 0;
        double cl = 0;
        Handle(Geom_Curve) c = BRep_Tool::Curve(edge, cf, cl);
        if (!c)
        {
            BRepLib::BuildCurve3d(edge);
            c = BRep_Tool::Curve(edge, cf, cl);
            if (!c)
            {
                return false;
            }
        }
    }
    return true;
}

void ViewWindow::RandomUpdateSurfDeformData() const
{
    for (size_t i = 0; i < _d->surfDeformOffsets.size(); i++)
    {
        int off = std::rand() % 3 + 1;
        double new_offset = _d->surfDeformOffsets[i] + _d->surfDeformOffsetSigns[i] * off;

        if (std::fabs(new_offset) > _d->surfDeformMaxOffset)
        {
            _d->surfDeformOffsetSigns[i] *= -1;
        }

        _d->surfDeformOffsets[i] += _d->surfDeformOffsetSigns[i] * off;
    }

    qDebug() << _d->surfDeformOffsets;
}

void ViewWindow::keyPressEvent(QKeyEvent* event)
{
    const int key = event->key();

    std::vector<TopoDS_Shape> shapes;
    std::vector<std::string> names;
    switch (key)
    {
        case Qt::Key_1:
        {
            TestHelix::TestPerf(shapes, names);
        } break;
        case Qt::Key_2:
        {
            TestHelix::Examples(shapes, names);
        } break;
        default:
            break;
    }

    std::hash<TopoDS_Shape> hasher;
    for (size_t i = 0; i < shapes.size() && i < names.size(); i++)
    {
        int color = hasher(shapes[i]) % 499;
        if (Quantity_NOC_BLUE == color)
        {
            color = Quantity_NOC_RED;
        }
        DrawShape(shapes[i], QString::fromStdString(names[i]), color);
    }
}
